package cz.stv.pecinovsky.správceplátna;

import cz.stv.pecinovsky.utility.IO;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;



/*******************************************************************************
 * Instance třídy {@code Řadič} zprostředkovádá ovládání z klávesnice
 * objektu implementujícího rozhrnaí {@code IOvládaný}.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 1.10.2678 — 2011-10-27
 */
public class Řadič extends KeyAdapter
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    private static final SprávcePlátna SP = SprávcePlátna.getInstance();

    private static final int[] IMPLICITNÍ_KLÁVESY =
                               {  KeyEvent.VK_LEFT,  KeyEvent.VK_RIGHT,
                                  KeyEvent.VK_UP,    KeyEvent.VK_DOWN,
                                  KeyEvent.VK_SPACE, KeyEvent.VK_ENTER,
                                  KeyEvent.VK_ESCAPE };

    private static final int VLEVO =0, VPRAVO=1, VZHŮRU=2, DOLŮ=3, 
                             MEZERA=4, ENTER=5,  KONEC=6,  POČET=KONEC+1;

    private static final String[] NÁZVY =
           { "VLEVO", "VPRAVO", "VZHŮRU", "DOLŮ", "MEZERA", "ENTER", "KONEC" };


    
//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================
//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================

    static {
        if ((IMPLICITNÍ_KLÁVESY.length != NÁZVY.length)  ||
            (IMPLICITNÍ_KLÁVESY.length != POČET))
        {
            throw new RuntimeException(
                    "\nNesouhlasí počet požadovaných kláves a jejich názvů");
        }
    }



//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    private final IOvládaný ovládaný;

    private final int[] klávesy;

    private final KeyListener posluchač;

    
    
//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================

    /***************************************************************************
     * Vrátí pole s implicitními klávesami uvedenými v pořadí
     * vlevo, vpravo, vzhůru, dolů, mezera, enter, konec.
     *
     * @return Pole s implicitními klávesami
     */
    public static int[] getImplicitníKlávesy()
    {
        return IMPLICITNÍ_KLÁVESY.clone();
    }

    

//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

    /***************************************************************************
     * Zjistí od uživateli, kterými klávesami by chtěl ovládat zadané funkce,
     * a vrátí pole s kódy zadaných kláves.
     * 
     * @param názvy Názvy jednotlivých funkcí
     * @return Pole s kódy zadaných kláíves
     */
    public static int[] zjistiKlávesy(String[] názvy)
    {
        final String[] výzvy = očíslujNázvy(názvy, true);
        oknoSVýzvou(výzvy);
        final Thread čekačka = new Thread("Čekačka") {
            @Override
            public void run()
            {
                try {
                    sleep(123456);
                }catch(InterruptedException ie) {
                    return;
                }
                System.out.println("Nedočkal jsem se - končím");
            }
        };
        final int   počet  = názvy.length;
        final int[] zadané = new int[počet];
        final int[] pořadí = new int[1];
        KeyListener kl = new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e)
            {
                int další = ++pořadí[0];
                int kc = e.getKeyCode();
                zadané[další-1] = kc;

                if (další == počet) {
                    System.out.println(KeyEvent.getKeyText(kc)+" - kód: " + kc);
                    SP.odhlašKlávesnici(this);
                    čekačka.interrupt();
                }
                else {
                    System.out.print(KeyEvent.getKeyText(kc) +
                                     " - kód: " + kc + výzvy[další]);
                }
            }
        };
        String text = "\nZačínáme:" + výzvy[0];
        System.out.print(text);
        čekačka.start();
        SP.přihlašKlávesnici(kl);
        try {
            čekačka.join();
        } catch (InterruptedException ex) {
            //Nerní co řešit - když mne přeruší, tak končím
        }
        if (pořadí[0] == počet) {
            return zadané;
        }        
        return null;
    }
   


//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================
    
    /***************************************************************************
     * Požádá uživatele o zadání kláves a pak vytvoří řadič,
     * který bude reagovat na stisky těchto kláves
     * a na jejich základě ovládat zadaný objekt.
     * 
     * @param ovládaný Objekt ovládaný z klávesnice prostřednictvím řadiče
     * @return Požadovaný řadič
     */
    public static Řadič vytvořPro(IOvládaný ovládaný)
    {
        int[] zadané = zjistiKlávesy(NÁZVY);
        Řadič řadič  = new Řadič(ovládaný, zadané, false);
        return řadič;
    }

    
    /***************************************************************************
     * Vytvoří nový řadič ovládaného objektu s implicitním nastavením kláves
     * a ihned jej přihlásí jako posluchače klávesnice.
     * 
     * @param ovládaný Ovládaný objekt
     */
    public Řadič(IOvládaný ovládaný)
    {
        this(ovládaný, IMPLICITNÍ_KLÁVESY, true);
    }


    /***************************************************************************
     * Vytvoří nový řadič ovládaného objektu s implicitním nastavením kláves.
     *
     * @param ovládaný  Ovládaný objekt
     * @param přihlásit Zda se má vytvořený řadič ihned přihlásit jako posluchač
     */
    public Řadič(IOvládaný ovládaný, boolean přihlásit)
    {
        this(ovládaný, IMPLICITNÍ_KLÁVESY, true);
    }


    /***************************************************************************
     * Vytvoří nový řadič ovládaného objektu se zadaným nastavením kláves.
     * 
     * @param ovládaný  Ovládaný objekt
     * @param klávesy   Pole kódy kláves, na něž se má reagovat
     * @param přihlásit Zda se má vytvořený řadič ihned přihlásit jako posluchač
     */
    public Řadič(IOvládaný ovládaný, int[] klávesy, boolean přihlásit)
    {
        if (klávesy.length != IMPLICITNÍ_KLÁVESY.length) {
            throw new RuntimeException(
                    "\nŠpatný počet ovládacích kláves -\nŘadič je připraven " +
                    "ovládat svěřené objekty prostřednictvím právě " +
                    IMPLICITNÍ_KLÁVESY.length + " kláves");

        }
        this.ovládaný = ovládaný;
        this.klávesy  = klávesy;
        this.posluchač = new PosluchačIf();
        SP.přidej(ovládaný);
        if (přihlásit) {
            start();
        }
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================

    /***************************************************************************
     * Vrátí odkaz na ovládaný objekt.
     *
     * @return Odkaz na ovládaný objekt
     */
    public IOvládaný getOvládaný()
    {
        return ovládaný;
    }



//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Odhlásí se jako posluchač klávesnice.
     * Pro opětovné přihlášení slouží metoda {@link #start()}.
     */
    public void konec()
    {
        SP.odhlašKlávesnici(posluchač);
    }


    /***************************************************************************
     * Přihlhlásí se jako posluchač klávesnice.
     * Po tomto odhlášení se již nemůže opět přihlásit.
     */
    public final void start()
    {
        SP.přidejNavrch(ovládaný);
        SP.přihlašKlávesnici(posluchač);
    }


    /***************************************************************************
     * Vrací textovou reprezentaci (podpis) dané instance
     * používanou především k ladicím účelům.
     *
     * @return Požadovaná textová reprezentace
     */
    @Override
    public String toString()
    {
        return "Řadič_(ovládaný=" + ovládaný + ")";
    }



//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
    
    /***************************************************************************
     * Zobrazí okno s vypsanými popisky jednotlivých zadávaných kláves.
     * 
     * @param texty Testy vypisované v dialogovém okně
     */
    private static void oknoSVýzvou(String[] texty)
    {
        int počet = texty.length;
        StringBuilder sb = new StringBuilder(
                                "Stiskem OK otvrďte přečtení této zprávy" +
                              "\na pak postupně stiskněte ");
        sb.append(počet).append(" kláves\ns významy v následujícím pořadí\n");
        for (int i=0;   i < počet;   i++) {
            sb.append(texty[i]);
        }
        sb.append("\n\nStandardní výstup vás bude navigovat");
        IO.zpráva(sb.toString());
    }
 
    /***************************************************************************
     * Vráti pole, v němž budou očíslované texty ze zadaného pole.
     * Texty ve vráceném poli budou číslovány od jedničky.
     * 
     * @param názvy    Texty, které je třeba očíslovat
     * @param newLine  Má-li se na začátek očíslovaných textů vkládat new line
     * @return Pole s očíslovanými texty
     */
    private static String[] očíslujNázvy(String[] názvy, boolean newLine)
    {
        int počet = názvy.length;
        String nl = newLine  ?  "\n"  :  "";
        String[] očíslované  = new String[počet];
        for (int i=0;   i < počet;   i++) {
            očíslované[i] = nl + (i+1) + ". " + názvy[i] + ": ";
        }
        return očíslované;
    }
    
    

//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================
//== INTERNÍ DATOVÉ TYPY =======================================================

    /***************************************************************************
     * Instance třídy PosluchačCase slouží jako posluchač klávesnice
     * řídící pohyb UFO v druhé variantě hry.
     */
    private
    class PosluchačCase extends KeyAdapter
    {
        /***********************************************************************
         * Ošetření klávesnice.
         */
        @Override
        public void keyPressed(KeyEvent e)
        {
            int kc = e.getKeyCode();
            switch(kc)
            {
                case KeyEvent.VK_LEFT:
                    ovládaný.vlevo();
                    return;

                case KeyEvent.VK_RIGHT:
                    ovládaný.vpravo();
                    return;

                case KeyEvent.VK_UP:
                    ovládaný.vzhůru();
                    return;

                case KeyEvent.VK_DOWN:
                    ovládaný.dolů();
                    return;

                case KeyEvent.VK_SPACE:
                    ovládaný.mezera();
                    return;

                case KeyEvent.VK_ENTER:
                    ovládaný.enter();
                    return;

                case KeyEvent.VK_ESCAPE:
                    ovládaný.konec();
                    Řadič.this.konec();
                    return;
            }
        }

    }

    

    /***************************************************************************
     * Instance třídy PosluchačCase slouží jako posluchač klávesnice
     * řídící pohyb UFO v druhé variantě hry.
     */
    private
    class PosluchačIf extends KeyAdapter
    {
        /***********************************************************************
         * Ošetření klávesnice.
         */
        @Override
        public void keyPressed(KeyEvent e)
        {
            int kc = e.getKeyCode();
            if (kc == klávesy[VLEVO]) {
                ovládaný.vlevo();
            }
            else if (kc == klávesy[VPRAVO]) {
                ovládaný.vpravo();
            }
            else if (kc == klávesy[VZHŮRU]) {
                ovládaný.vzhůru();
            }
            else if (kc == klávesy[DOLŮ]) {            
                ovládaný.dolů();
            }
            else if (kc == klávesy[MEZERA]) {
                ovládaný.mezera();
            }
            else if (kc == klávesy[ENTER]) {
                ovládaný.enter();
            }
            else if (kc == klávesy[KONEC]) {
                ovládaný.konec();
                Řadič.this.konec();
            }
        }
    }
    
    
    
//== TESTY A METODA MAIN =======================================================
}
